package com.hqd.ch03.v49.boot.context.config;

import java.io.IOException;
import java.util.*;

/**
 * 配置导入
 */
public class ConfigDataImporter {

    /**
     * 配置资源定位器
     */
    private final ConfigDataLocationResolvers resolvers;

    /**
     * 配置加载器
     */
    private final ConfigDataLoaders loaders;

    /**
     * 配置未找到时回调
     */
    private final ConfigDataNotFoundAction notFoundAction;

    /**
     * 配置文件资源
     */
    private final Set<ConfigDataResource> loaded = new HashSet<>();

    /**
     * 配置文件路径
     */
    private final Set<ConfigDataLocation> loadedLocations = new HashSet<>();

    /**
     * 可选配置文件路径
     */
    private final Set<ConfigDataLocation> optionalLocations = new HashSet<>();

    public ConfigDataImporter(ConfigDataNotFoundAction notFoundAction,
                              ConfigDataLocationResolvers resolvers, ConfigDataLoaders loaders) {
        this.resolvers = resolvers;
        this.loaders = loaders;
        this.notFoundAction = notFoundAction;
    }

    public Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataLocation... locations) {
        return this.resolveAndLoad(null, null, null, Arrays.asList(locations));
    }

    public Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
                                                                      ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
                                                                      List<ConfigDataLocation> locations) {
        try {
            /**
             * 多环境
             */
            Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
            //定位资源
            List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
            //加载资源
            return load(loaderContext, resolved);
        } catch (IOException ex) {
            throw new IllegalStateException("IO error on loading imports from " + locations, ex);
        }
    }

    private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
                                                     Profiles profiles, List<ConfigDataLocation> locations) {
        List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
        /**
         * 遍历所有路径
         */
        for (ConfigDataLocation location : locations) {
            resolved.addAll(resolve(locationResolverContext, profiles, location));
        }
        return Collections.unmodifiableList(resolved);
    }

    private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
                                                     Profiles profiles, ConfigDataLocation location) {
        try {
            return this.resolvers.resolve(locationResolverContext, location, profiles);
        } catch (ConfigDataNotFoundException ex) {
            handle(ex, location, null);
            return Collections.emptyList();
        }
    }

    private Map<ConfigDataResolutionResult, ConfigData> load(ConfigDataLoaderContext loaderContext,
                                                             List<ConfigDataResolutionResult> candidates) throws IOException {
        Map<ConfigDataResolutionResult, ConfigData> result = new LinkedHashMap<>();
        for (int i = candidates.size() - 1; i >= 0; i--) {
            ConfigDataResolutionResult candidate = candidates.get(i);
            ConfigDataLocation location = candidate.getLocation();
            ConfigDataResource resource = candidate.getResource();
            /**
             * 是否可选
             */
            if (resource.isOptional()) {
                this.optionalLocations.add(location);
            }
            if (this.loaded.contains(resource)) {
                this.loadedLocations.add(location);
            } else {
                try {
                    /**
                     * 加载配置
                     */
                    ConfigData loaded = this.loaders.load(loaderContext, resource);
                    if (loaded != null) {
                        this.loaded.add(resource);
                        this.loadedLocations.add(location);
                        result.put(candidate, loaded);
                    }
                } catch (ConfigDataNotFoundException ex) {
                    handle(ex, location, resource);
                }
            }
        }
        return Collections.unmodifiableMap(result);
    }

    private void handle(ConfigDataNotFoundException ex, ConfigDataLocation location, ConfigDataResource resource) {
    }

    private ConfigDataNotFoundAction getNotFoundAction(ConfigDataLocation location, ConfigDataResource resource) {
        if (location.isOptional() || (resource != null && resource.isOptional())) {
            return ConfigDataNotFoundAction.IGNORE;
        }
        return this.notFoundAction;
    }

    Set<ConfigDataLocation> getLoadedLocations() {
        return this.loadedLocations;
    }

    Set<ConfigDataLocation> getOptionalLocations() {
        return this.optionalLocations;
    }

}

